package redis

import (
	"errors"
	"fmt"
	"time"

	"github.com/gogf/gf/database/gredis"
	"github.com/gogf/gf/encoding/gjson"
	"github.com/gogf/gf/util/gconv"
	"github.com/gomodule/redigo/redis"
)

const (
	REDIS_OP_SET             = "SET"
	REDIS_OP_GET             = "GET"
	REDIS_OP_LIST_LLEN       = "LLEN"
	REDIS_OP_LIST_LPUSH      = "LPUSH"
	REDIS_OP_LIST_RPUSH      = "RPUSH"
	REDIS_OP_LIST_LPOP       = "LPOP"
	REDIS_OP_LIST_RPOP       = "RPOP"
	REDIS_OP_CHANNEL_PUBLISH = "PUBLISH"
	REDIS_OP_EXISTS          = "EXISTS"
	REDIS_OP_DELETE          = "DEL"
	REDIS_OP_ZADD            = "ZADD"
	REDIS_OP_ZINCRBY         = "ZINCRBY"
	REDIS_OP_TTL             = "TTL"
)

var redisPool *redis.Pool

func SetConfig(config gredis.Config) {
	redisPool = &redis.Pool{
		MaxActive:   config.MaxActive,
		MaxIdle:     config.MaxIdle,
		IdleTimeout: config.IdleTimeout,
		Dial: func() (redis.Conn, error) {
			address := fmt.Sprintf("%v:%v", config.Host, config.Port)
			db := redis.DialDatabase(config.Db)
			pass := redis.DialPassword(config.Pass)
			readTimeout := redis.DialReadTimeout(time.Duration(config.ConnectTimeout))
			writeTimeout := redis.DialWriteTimeout(time.Duration(config.ConnectTimeout))
			connTimeout := redis.DialConnectTimeout(time.Duration(config.ConnectTimeout))
			c, err := redis.Dial("tcp", address, db, pass, readTimeout, writeTimeout, connTimeout)
			if err != nil {
				return nil, fmt.Errorf("redis connection error: %s", err)
			}
			return c, err
		},
		TestOnBorrow: func(c redis.Conn, t time.Time) error {
			if time.Since(t) < time.Minute {
				return nil
			}
			_, err := c.Do("PING")
			return err
		},
		Wait: true,
	}
}

func SETEX(key string, i interface{}, ex int64) error {
	con := redisPool.Get()
	defer con.Close()
	_var, err := con.Do(REDIS_OP_SET, key, gconv.String(i), "EX", ex)
	if err != nil {
		return err
	}
	_str := gconv.String(_var)
	if _str == "OK" {
		return nil
	}
	return errors.New(_str)
}

func SET(key string, i interface{}) error {
	con := redisPool.Get()
	defer con.Close()
	_var, err := con.Do(REDIS_OP_SET, key, gconv.String(i))
	if err != nil {
		return err
	}
	_str := gconv.String(_var)
	if _str == "OK" {
		return nil
	}
	return errors.New(_str)
}

func GET(key string) (reply interface{}, err error) {
	con := redisPool.Get()
	defer con.Close()
	reply, err = con.Do(REDIS_OP_GET, key)
	return reply, err
}

func GETObj(key string, i interface{}) error {
	_var, err := GET(key)
	if err != nil {
		return err
	}
	return gjson.DecodeTo(_var, i)
}

func DEL(key string) error {
	con := redisPool.Get()
	defer con.Close()
	_, err := con.Do(REDIS_OP_DELETE, key)
	return err
}

func EXISTS(key string) (bool, error) {
	con := redisPool.Get()
	defer con.Close()
	exists, err := con.Do(REDIS_OP_EXISTS, key)
	if err != nil {
		return false, err
	}
	return exists.(int64) == 1, nil
}

func LPUSH(queue string, i interface{}) (err error) {
	if i == nil {
		return fmt.Errorf("LPUSH item is nil")
	}
	con := redisPool.Get()
	defer con.Close()
	_, err = con.Do(REDIS_OP_LIST_LPUSH, queue, gconv.String(i))
	return err
}

func RPUSH(queue string, i interface{}) (err error) {
	if i == nil {
		return fmt.Errorf("RPUSH item is nil")
	}
	con := redisPool.Get()
	defer con.Close()
	_, err = con.Do(REDIS_OP_LIST_RPUSH, queue, gconv.String(i))
	return err
}

func LPOP(queue string) (reply interface{}, err error) {
	con := redisPool.Get()
	defer con.Close()
	reply, err = con.Do(REDIS_OP_LIST_LPOP, queue)
	return reply, err
}

func RPOP(queue string) (reply interface{}, err error) {
	con := redisPool.Get()
	defer con.Close()
	reply, err = con.Do(REDIS_OP_LIST_RPOP, queue)
	return reply, err
}

func ZADD(key string, score float64, member string) (reply interface{}, err error) {
	con := redisPool.Get()
	defer con.Close()
	reply, err = con.Do(REDIS_OP_ZADD, key, score, member)
	return reply, err
}

func ZINCRBY(key string, score float64, member string) (reply interface{}, err error) {
	con := redisPool.Get()
	defer con.Close()
	reply, err = con.Do(REDIS_OP_ZINCRBY, key, score, member)
	return reply, err
}

func TTL(key string) (int64, error) {
	con := redisPool.Get()
	defer con.Close()
	ttl, err := con.Do(REDIS_OP_TTL, key)
	if err != nil {
		return -1, err
	}
	return ttl.(int64), nil
}
